代码坏味道及其重构 2 重复代码
特征
- 重复的代码
- 重复的常量
出现的原因
- 直接复制粘贴代码;
- 团队开发时,不同的人写了相同的代码;
引起的问题
修改、理解代码时,需要阅读所有的代码副本,然后修改所有的代码副本,漏改就会引起 bug。
重构方法
移动语句
把相似结构的代码放到一起,方便提炼函数。
// 重构前
const pricingPlan = retrievePricingPlan();
const order = retreiveOrder();
let charge;
const chargePerUnit = pricingPlan.unit;
// 重构后
const pricingPlan = retrievePricingPlan();
const chargePerUnit = pricingPlan.unit;
const order = retreiveOrder();
let charge;
让存在关联的代码一起出现,更加容易理解。如果几行代码访问了同一个数据,那就放在一起。
提炼函数
把重复的、不同层次的代码提炼成函数。
// 重构前
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding();
//print details
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
}
// 重构后
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding();
printDetails(outstanding);
function printDetails(outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
}
}
如果一个函数的代码需要你去阅读一下才能知道它到底在干什么,那就提取函数。保持尽量小的函数。同时,小函数要起一个好的名字。
函数上移
重复的子类函数,可以上移到父类。
// 重构前
class Employee {...}
class Salesman extends Employee {
get name() {...}
}
class Engineer extends Employee {
get name() {...}
}
// 重构后
class Employee {
get name() {...}
}
class Salesman extends Employee {...}
class Engineer extends Employee {...}
如果一个函数在多个子类中的函数实现都相同,那就把它提到父类。
如果几个子类中的多个函数可以通过某种形式的参数调整为一个函数,那就先对这些函数采取函数参数化,然后使用函数上移。
如果涉及子类的字段或其他函数,可能需要使用字段上移、函数上移先重构这些特性。
示例代码
例子 1,重复结构的代码:
// 重构前
public void sendBook() {
try {
this.service.sendBook();
} catch (Throwable t) {
this.notification.send(new SendFailure(t)));
throw t;
}
}
public void sendChapter() {
try {
this.service.sendChapter();
} catch (Throwable t) {
this.notification.send(new SendFailure(t)));
throw t;
}
}
// 重构后
private void executeTask(final Runnable runnable) {
try {
runnable.run();
} catch (Throwable t) {
this.notification.send(new SendFailure(t)));
throw t;
}
}
public void sendBook() {
executeTask(this.service::sendBook);
}
public void sendChapter() {
executeTask(this.service::sendChapter);
}
例子 2:
// 重构前
if (Utils.isPad()) {
title.setText(R.string.pad_txt);
} else {
title.setText(R.string.phone_txt);
}
// 重构后
int titleStrId = Utils.isPad() ? R.string.pad_txt : R.string.phone_txt;
title.setText(titleStrId);